package cn.ctyun.cloudflow.sdk.http;

import cn.ctyun.cloudflow.sdk.util.OkHttpUtil;
import cn.ctyun.cloudflow.sdk.util.Signer;
import okhttp3.*;

import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

public class CTApiClient {
    private final Credential credential;
    private final String endpoint;
    private final String region;

    private final OkHttpClient client;
    private final DateFormat eopDateFmt = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");

    public CTApiClient(Builder builder) {
        this.endpoint = builder.endpoint;
        this.credential = builder.credential;
        this.region = builder.region;
        HttpConfig httpConfig = builder.httpConfig;
        OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
        if (httpConfig != null) {
            if (httpConfig.getConnectTimeout() >= 0) {
                clientBuilder.connectTimeout(httpConfig.getConnectTimeout(), TimeUnit.SECONDS);
            }
            if (httpConfig.getReadTimeout() >= 0) {
                clientBuilder.readTimeout(httpConfig.getReadTimeout(), TimeUnit.SECONDS);
            }
            if (httpConfig.getWriteTimeout() >= 0) {
                clientBuilder.writeTimeout(httpConfig.getWriteTimeout(), TimeUnit.SECONDS);
            }
        }

        // 忽略SSL证书校验
        clientBuilder.sslSocketFactory(OkHttpUtil.getIgnoreInitedSslContext().getSocketFactory(), OkHttpUtil.X_509_TRUST_MANAGER)
                .hostnameVerifier(OkHttpUtil.getIgnoreSslHostnameVerifier());

        client = clientBuilder.build();
    }

    public String request(HttpMethod method, String path, Headers headers, String body) throws IOException {
        String url = this.endpoint + path;
        Request request = getRequest(method, url, headers, body);
        try (Response response = client.newCall(request).execute()) {
            String responseBody = getResponseBody(response);
            if (!response.isSuccessful()) {
                throw new IOException("Unexpected response code: " + response.code() + ", message: " + responseBody);
            }
            return responseBody;
        }
    }

    private String getResponseBody(Response response) throws IOException {
        ResponseBody responseBody = response.body();
        if (responseBody == null) {
            return null;
        }
        return responseBody.string();
    }

    private Request getRequest(HttpMethod method, String url, Headers headers, String body) {
        Request.Builder builder = new Request.Builder().url(url);
        switch (method) {
            case POST:
                builder.post(jsonRequestBody(body));
                break;
            case PUT:
                builder.put(jsonRequestBody(body));
                break;
            case PATCH:
                builder.patch(jsonRequestBody(body));
                break;
            case DELETE:
                builder.delete(jsonRequestBody(body));
                break;
            case HEAD:
                builder.head();
                break;
            default:
                builder.get();
        }
        builder.headers(signHeader(headers, url, body));
        if (this.region != null && !this.region.isEmpty()) {
            builder.header("regionId", this.region);
        }
        return builder.build();
    }

    private  RequestBody jsonRequestBody(String body) {
        if (body == null) {
            body = "";
        }
        return RequestBody.Companion.create(body, MediaType.parse("application/json"));
    }

    private Headers signHeader(Headers headers, String url, String body) {
        String sortQuery = sortQuery(url);
        String eopDate = eopDateFmt.format(new Date());
        String requestId = UUID.randomUUID().toString();

        String eopAuthorization;
        try {
            eopAuthorization = Signer.getEopAuthorization(eopDate, sortQuery, body, this.credential.getAk(), this.credential.getSk(), requestId);
        } catch (InvalidKeyException | NoSuchAlgorithmException e) {
            throw new RuntimeException("get eopAuthorization failed", e);
        }

        if (headers == null) {
            headers = new Headers.Builder().build();
        }
        return new Headers.Builder().addAll(headers)
                .set("Eop-Date", eopDate)
                .set("Ctyun-Eop-Request-Id",requestId)
                .set("Eop-Authorization", eopAuthorization)
                .build();
    }

    private String sortQuery(String urlString) {
        HttpUrl url = HttpUrl.parse(urlString);
        if (url != null) {
            StringBuilder builder = new StringBuilder();
            Set<String> names = url.queryParameterNames();
            names.stream().sorted().forEach(key -> {
                List<String> values = url.queryParameterValues(key);
                if (!values.isEmpty()) {
                    values.stream().filter(value -> value != null && !value.isEmpty()).forEach(
                            value -> builder.append("&").append(key).append("=").append(value)
                    );
                }
            });
            if (builder.length() > 0) {
                return builder.substring(1);
            }
        }
        return "";
    }

    public static class Builder {
        private Credential credential;
        private HttpConfig httpConfig;
        private String endpoint;
        private String region;

        public Builder withCredential(Credential credential) {
            this.credential = credential;
            return this;
        }
        public Builder withRegion(String region) {
            this.region = region;
            return this;
        }
        public Builder withEndpoint(String endpoint) {
            this.endpoint = endpoint;
            return this;
        }
        public Builder withHttpConfig(HttpConfig httpConfig) {
            this.httpConfig = httpConfig;
            return this;
        }
        public CTApiClient build() {
            return new CTApiClient(this);
        }
    }
}
